home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / plugins / bspstudy.py < prev    next >
Text File  |  2004-01-05  |  18KB  |  596 lines

  1.  
  2. import quarkx
  3. import quarkpy.qbaseeditor
  4. import quarkpy.mapmenus
  5. import quarkpy.bspcommands
  6. import quarkpy.maphandles
  7. import quarkpy.mapentities
  8. import quarkpy.dlgclasses
  9. import mapmadsel
  10.  
  11. from quarkpy.maputils import *
  12.  
  13. #
  14. #  This one is to identify the planes that have some
  15. #   other lying close to them; it takes only the first
  16. #   from each pair.  A 'NearPlanesDlg' below gets the
  17. #   planes that are close to an already given one.
  18. #
  19. class ClosePlanesDlg (quarkpy.dlgclasses.LiveEditDlg):
  20.     #
  21.     # dialog layout
  22.     #
  23.  
  24.     endcolor = AQUA
  25.     size = (220,200)
  26.     dfsep = 0.35
  27.     dlgflags = FWF_KEEPFOCUS 
  28.  
  29.     dlgdef = """
  30.         {
  31.         Style = "9"
  32.         Caption = "Close Plane Finder"
  33.  
  34.         closeplanes: = {
  35.           Typ = "C"
  36.           Txt = "Planes:"
  37.           Items = "%s"
  38.           Values = "%s"
  39.           Hint = "These are the planes that are too close to others.  Pick one," $0D " then push buttons on row below for action."
  40.         }
  41.  
  42.         normal: = {
  43.           Typ = "EF00003"
  44.           Txt = "Normal"
  45.           Hint = "The normal of the chosen plane"
  46.         }
  47.         
  48.         dist: = {
  49.           Typ = "EF00001"
  50.           Txt = "dist"
  51.           Hint = "The dist value of the chosen plane"
  52.         }
  53.         
  54.         sep: = { Typ="S" Txt=""}
  55.  
  56.         buttons: = {
  57.           Typ = "PM"
  58.           Num = "2"
  59.           Macro = "closeplanes"
  60.           Caps = "SN"
  61.           Txt = "Actions:"
  62.           Hint1 = "Show the chosen one (point & normal)"
  63.           Hint2 = "Get the planes near the chosen one"
  64.         }
  65.  
  66.         num: = {
  67.           Typ = "EF1"
  68.           Txt = "# found"
  69.         }
  70.  
  71.         close: = {
  72.           Typ = "EF001"
  73.           Txt = "tolerance: "
  74.           Hint = "Planes whose dist*norm difference is less than this are deemed suspicious."
  75.         }
  76.         
  77.         sep: = { Typ="S" Txt=""}
  78.  
  79.         exit:py = {Txt="" }
  80.     }
  81.     """
  82.  
  83.     def inspect(self):
  84.         self.editor.layout.explorer.uniquesel = self.pack.plane
  85.         
  86.     def nearplanes(self):
  87.         index = eval(self.chosen)
  88.         plane = self.pack.closeones[index]
  89.         nearPlanesClickFunc(None,plane,self.editor)
  90.  
  91.  
  92. def macro_closeplanes(self, index=0):
  93.     editor = mapeditor()
  94.     if editor is None: return
  95.     if index==1:
  96.         editor.closeplanesdlg.inspect()
  97.  
  98.     elif index==2:
  99.         editor.closeplanesdlg.nearplanes()
  100.         
  101. quarkpy.qmacro.MACRO_closeplanes = macro_closeplanes
  102.  
  103.  
  104. def PlanesClick(m):
  105.     editor=mapeditor()
  106.     root = editor.Root
  107.     planes = editor.Root.parent.planes
  108.     planegroup = quarkx.newobj("Planes (%d):g"%len(planes))
  109.     undo=quarkx.action()
  110.     undo.put(root,planegroup)
  111.     i=0
  112.     for plane in planes:
  113. #       debug('plane '+plane.shortname+': '+`plane['norm']`)
  114. #       debug('  dist: '+"%2f"%plane['dist'])
  115.        undo.put(planegroup,plane)
  116.     editor.ok(undo, 'get planes')
  117.     editor.layout.explorer.uniquesel=planegroup
  118.     editor.planes = planegroup
  119.  
  120.  
  121. def getClosePlanes(close, editor):
  122.     closeones=[]
  123.     planes = editor.planes.subitems
  124.     indexes = editor.Root.parent.closeplanes(close)
  125.     for i in indexes:
  126.         closeones.append(planes[i])
  127.     return closeones
  128.  
  129. def CheckPlanesClick(m):
  130.     editor=mapeditor()
  131.     try:
  132.         planes=editor.planes.subitems
  133.     except (AttributeError):
  134.         PlanesClick(m)
  135.         planes=editor.planes.subitems
  136.  
  137.     close = quarkx.setupsubset(SS_MAP, "Options")["planestooclose"]
  138.     if close==None:
  139.         close="1.0"
  140.     close=eval(close)
  141.     
  142.     closeones = getClosePlanes(close, editor)
  143. #    editor.layout.explorer.sellist=closeones
  144. #    quarkx.msgbox("%d suspicious planes found"%len(closeones),2,4)
  145.  
  146.     class pack:
  147.       "stick stuff here"
  148.     pack.close=close
  149.     pack.closeones=closeones
  150.  
  151.     def setup(self, pack=pack, editor=editor):
  152.         self.pack=pack
  153.         #
  154.         # Part of the convolution for the buttons, to communicate
  155.         #  which objects methods should be called when one pushed.
  156.         # Cleaned up in onclosing below.
  157.         #
  158.         editor.closeplanesdlg=self
  159.         #
  160.         # Names and list-indexes of close planes
  161.         #
  162.         ran = range(len(pack.closeones))
  163.         pack.slist = map(lambda obj,num:"%d) %s"(num+1,obj.shortname), pack.closeones, ran)
  164.         pack.klist = map(lambda d:`d`, ran)
  165.         self.src["closeplanes$Items"] = "\015".join(pack.slist)
  166.         self.src["closeplanes$Values"] = "\015".join(pack.klist)
  167.         #
  168.         # Note the commas, EF..1 controls take 1-tuples as data
  169.         #
  170.         self.src["num"]=len(pack.klist),
  171.         self.src["close"]=pack.close,
  172.  
  173.     def action(self, pack=pack, editor=editor):
  174.         src = self.src
  175.         #
  176.         # note what's been chosen
  177.         #
  178.         self.chosen = src["closeplanes"]
  179.         plane = self.pack.closeones[eval(self.chosen)]
  180.         self.pack.plane=plane
  181. #        debug('norm '+`plane.normal`+' dist '+`plane.dist`)
  182.         src["normal"]=plane.normal.tuple
  183.         src["dist"]=plane.dist,
  184.         #
  185.         # see if thinness threshold has been changed
  186.         #
  187.         newclose, = self.src["close"]
  188.         if newclose!=pack.close:
  189.             if newclose==1.0:
  190.                 quarkx.setupsubset(SS_MAP, "Options")["planestooclose"]=None
  191.             else:
  192.                 quarkx.setupsubset(SS_MAP, "Options")["planestooclose"]="%f2"%newclose
  193.  
  194.             pack.close="%.2f"%newclose
  195.             pack.closeones=getClosePlanes(newclose, editor)
  196.  
  197.     #
  198.     # Cleanup when dialog closes (not needed if no mess has
  199.     #  been created)
  200.     #
  201.     def onclosing(self,editor=editor):
  202.         del editor.closeplanesdlg
  203.     
  204.     ClosePlanesDlg(quarkx.clickform, 'closeplanes', editor, setup, action, onclosing)
  205.  
  206.     
  207. def NodesClick(m,editor=None):
  208.     if editor is None:
  209.         editor=mapeditor()
  210.     if editor is None:
  211.         return
  212.     try:
  213.         nodes=editor.nodes
  214.         quarkx.msgbox("Nodes already gotten",2,4)
  215.         editor.layout.explorer.uniquesel=nodes
  216.     except:
  217.         root = editor.Root
  218.         nodes = root.parent.nodes
  219.         nodes.shortname='Nodes (%s)'%nodes['children']
  220.         undo=quarkx.action()
  221.         undo.put(root,nodes)
  222.         editor.ok(undo, 'get nodes')
  223.         editor.layout.explorer.uniquesel=nodes
  224.         editor.nodes=nodes
  225.   
  226. planesitem=qmenu.item('Get Planes',PlanesClick)
  227. nodesitem=qmenu.item('Get Nodes',NodesClick)
  228. planecheckitem=qmenu.item('Check Planes',CheckPlanesClick)
  229.  
  230. quarkpy.bspcommands.items.append(planesitem)
  231. quarkpy.bspcommands.items.append(nodesitem)
  232. quarkpy.bspcommands.items.append(planecheckitem)
  233.  
  234.  
  235. #
  236. #  This one is to identify the planes that lie close
  237. #    to a specified one.  ClosePlanesDlg above identifies
  238. #    planes that have others near them.  Uses same
  239. #    tolerance as that one.
  240. #
  241. class NearPlanesDlg (quarkpy.dlgclasses.LiveEditDlg):
  242.     #
  243.     # dialog layout
  244.     #
  245.  
  246.     endcolor = AQUA
  247.     size = (220,200)
  248.     dfsep = 0.35
  249.     dlgflags = FWF_KEEPFOCUS 
  250.  
  251.     dlgdef = """
  252.         {
  253.         Style = "9"
  254.         Caption = "Near Planes"
  255.  
  256.         nearplanes: = {
  257.           Typ = "C"
  258.           Txt = "Planes:"
  259.           Items = "%s"
  260.           Values = "%s"
  261.           Hint = "These are the planes that are near the given one.  Pick one," $0D " then push buttons on row below for action."
  262.         }
  263.  
  264.         normal: = {
  265.           Typ = "EF00003"
  266.           Txt = "Normal"
  267.           Hint = "The normal of the chosen plane"
  268.         }
  269.         
  270.         dist: = {
  271.           Typ = "EF00001"
  272.           Txt = "dist"
  273.           Hint = "The dist value of the chosen plane"
  274.         }
  275.         
  276.         sep: = { Typ="S" Txt=""}
  277.  
  278.         buttons: = {
  279.           Typ = "PM"
  280.           Num = "3"
  281.           Macro = "nearplanes"
  282.           Caps = "SCN"
  283.           Txt = "Actions:"
  284.           Hint1 = "Show the chosen one"
  285.           Hint2 = "Collect faces lying on the chosen one"
  286.           Hint3 = "Find nodes split by the chosen one"
  287.         }
  288.  
  289.         num: = {
  290.           Typ = "EF1"
  291.           Txt = "# found"
  292.         }
  293.         
  294.         close: = {
  295.           Typ = "EF001"
  296.           Txt = "tolerance: "
  297.           Hint = "Planes whose dist*norm difference is less than this are deemed suspicious."
  298.         }
  299.  
  300.         sep: = { Typ="S" Txt=""}
  301.  
  302.         exit:py = {Txt="" }
  303.     }
  304.     """
  305.  
  306.     def inspect(self):
  307.         self.editor.layout.explorer.uniquesel=self.pack.plane
  308.  
  309.     def collect(self):
  310.         index = eval(self.chosen)
  311.         plane = self.pack.nearones[index]
  312.         collectFacesClickFunc(None,plane,self.editor)
  313.  
  314.     def findsplit(self):
  315.         editor=self.editor
  316. #        debug('PLANE '+`self.pack.plane.num`)
  317.         findSplitNodes(self.editor,self.pack.plane)
  318.  
  319. def macro_nearplanes(self, index=0):
  320.     editor = mapeditor()
  321.     if editor is None: return
  322.     if index==1:
  323.         editor.nearplanesdlg.inspect()
  324.     elif index==2:
  325.         editor.nearplanesdlg.collect()
  326.     elif index==3:
  327.         editor.nearplanesdlg.findsplit()
  328.  
  329.         
  330. quarkpy.qmacro.MACRO_nearplanes = macro_nearplanes
  331.  
  332. def findSplitNodes(editor, plane):
  333.     try:
  334.         node=editor.nodes
  335.     except (AttributeError):
  336.         NodesClick(None,editor)
  337.         node=editor.nodes
  338.     list=[]
  339.     findsplitnodes2(node, plane, list)
  340.     editor.invalidateviews()
  341.     mapmadsel.browseListFunc(editor,list)
  342.  
  343. def findsplitnodes2(node, plane, list,n=0):
  344.     if plane.num == node.plane.num:
  345.         list.append(node)
  346. #        debug('append '+`node.plane.num`+'; '+`plane.num`)
  347.     for item in node.subitems:
  348.         if item.type==":bspnode":
  349.             if item.leaf!=1:
  350.                 findsplitnodes2(item, plane, list,n+1)
  351.  
  352.  
  353. def getNearPlanes(close, plane, editor):
  354.     indexes = plane.nearplanes(close,editor.Root.parent)
  355.     return map(lambda index,planes=editor.planes.subitems:planes[index], indexes)
  356.  
  357.  
  358. def nearPlanesClickFunc(m,o,editor):
  359.             #
  360.             # planes must have been collected for this
  361.             #  menu item to be available
  362.             #
  363.             close = quarkx.setupsubset(SS_MAP, "Options")["planestooclose"]
  364.             if close==None:
  365.                 close="1.0"
  366.             close=eval(close)
  367.  
  368.             nearones = getNearPlanes(close,o, editor)
  369.         #   debug('near '+`nearones`)
  370.         #    editor.layout.explorer.sellist=closeones
  371.         #    quarkx.msgbox("%d suspicious planes found"%len(closeones),2,4)
  372.  
  373.             class pack:
  374.               "stick stuff here"
  375.             pack.close=close
  376.             pack.nearones=nearones
  377.  
  378.             def setup(self, pack=pack, editor=editor, plane=o):
  379.                 self.pack=pack
  380.                 self.plane=plane
  381.                 #
  382.                 # Part of the convolution for the buttons, to communicate
  383.                 #  which objects methods should be called when one pushed.
  384.                 # Cleaned up in onclosing below.
  385.                 #
  386.                 editor.nearplanesdlg=self
  387.  
  388.                 #
  389.                 # Names and list-indexes of close planes
  390.                 #
  391.                 ran = range(len(pack.nearones))
  392.                 pack.slist = map(lambda obj, num:"%d) %s"%(num, obj.shortname, pack.nearones, ran))
  393.                 pack.klist = map(lambda d:`d`, ran)
  394.                 self.src["nearplanes$Items"] = "\015".join(pack.slist)
  395.                 self.src["nearplanes$Values"] = "\015".join(pack.klist)
  396.                 #
  397.                 # Note the commas, EF..1 controls take 1-tuples as data
  398.                 #
  399.                 self.src["num"]=len(pack.klist),
  400.                 self.src["close"]=pack.close,
  401.  
  402.             def action(self, pack=pack, editor=editor):
  403.                 src = self.src
  404.                 #
  405.                 # note what's been chosen
  406.                 #
  407.                 self.chosen = src["nearplanes"]
  408.                 plane = self.pack.nearones[eval(self.chosen)]
  409.                 self.pack.plane=plane
  410. #                debug('norm '+`plane.normal`+' dist '+`plane.dist`)
  411.                 src["normal"]=plane.normal.tuple
  412.                 src["dist"]=plane.dist,
  413.                 #
  414.                 # see if thinness threshold has been changed
  415.                 #
  416.                 newclose, = self.src["close"]
  417.                 if newclose!=pack.close:
  418.                     if newclose==1.0:
  419.                         quarkx.setupsubset(SS_MAP, "Options")["planestooclose"]=None
  420.                     else:
  421.                         quarkx.setupsubset(SS_MAP, "Options")["planestooclose"]="%f2"%newclose
  422.  
  423.                     pack.close="%.2f"%newclose
  424.                     pack.nearones=getNearPlanes(newclose, self.plane, editor)
  425.             
  426.             #
  427.             # Cleanup when dialog closes (not needed if no mess has
  428.             #  been created)
  429.             #
  430.             def onclosing(self,editor=editor):
  431.                 del editor.nearplanesdlg
  432.  
  433.             NearPlanesDlg(quarkx.clickform, 'nearplanes', editor, setup, action, onclosing)
  434.  
  435. def collectFacesClickFunc(m,o,editor):
  436.             faces = editor.Root.findallsubitems("",":f")
  437.             dist, normal = o.dist, o.normal
  438.             planept = dist*normal
  439.             coplanar=[]
  440.             i=0
  441.             for face in faces:
  442.               try:
  443.                 #
  444.                 # if the normals are equal or opposite
  445.                 #
  446.                 if math.fabs(normal*face.normal)>.999:
  447.                     #
  448.                     # and the plane points are identical:
  449.                     #
  450.                     if not(planept-face.dist*face.normal):
  451.                         coplanar.append(face)
  452.               except:
  453.                 pass
  454.               i=i+1;
  455. #            quarkx.msgbox(`i`+' faces tried',2,4)                    
  456.             quarkx.msgbox(`len(coplanar)`+' coplanar faces',2,4)            
  457.             editor.layout.explorer.sellist = coplanar
  458.  
  459. class PlaneType(quarkpy.mapentities.EntityManager):
  460.     "Bsp planes"
  461.  
  462.     def menu(o, editor):
  463.  
  464.         def collectFacesClick(m,o=o,editor=editor):
  465.             collectFacesClickFunc(m,o,editor)
  466.      
  467.         def nearPlanesClick(m,o=o,editor=editor):
  468.             nearPlanesClickFunc(m,o,editor)
  469.  
  470.         def splitNodesClick(m,o=o,editor=editor):
  471.             findSplitNodes(editor,o)
  472.             
  473.         collectItem=qmenu.item("Select Faces",collectFacesClick,"|Select the faces lying on this plane")
  474.         nearItem = qmenu.item("Near Planes",nearPlanesClick,"|Find the planes near this one")
  475.         splitItem = qmenu.item("Split Nodes",splitNodesClick,"|Find the nodes split by this plane")
  476.         return [collectItem, nearItem, splitItem]
  477.         
  478. class HullType(quarkpy.mapentities.GroupType):
  479.     "Bsp Hulls (models)"
  480.  
  481.     def menu(o, editor):
  482.  
  483.         def showFaces(m,o=o,editor=editor):
  484.             for face in o.subitems:
  485.                 face.flags = face.flags & ~2
  486.         
  487.         faceItem = qmenu.item("Show Faces",showFaces)
  488.         return [faceItem]
  489.  
  490. def nodeBox(o):
  491.     "viewed from positive z"
  492.     result = []
  493.     blb = quarkx.vect(o['mins'])
  494.     trf = quarkx.vect(o['maxs'])
  495.     blf = quarkx.vect(blb.x, trf.y, blb.z)
  496.     tlf = quarkx.vect(blb.x, trf.y, trf.z)
  497.     tlb = quarkx.vect(blb.x, blb.y, trf.z)
  498.     trb = quarkx.vect(trf.x, blb.y, trf.z)
  499.     brb = quarkx.vect(trf.x, blb.y, blb.z)
  500.     brf = quarkx.vect(trf.x, trf.y, blb.z)
  501.     for triple in ((tlb,tlf, blf), (brf, trf, trb),
  502.                    (tlf, tlb, trb), (brb, blb, blf),
  503.                    (tlf, trf, brf), (brb, trb, tlb)):
  504.         face = quarkx.newobj('box face:f')
  505.         face.setthreepoints(triple,0)
  506.         result.append(face)
  507.     return result
  508.  
  509.  
  510. def nodePoly(o):
  511.     if o['empty']:
  512.         return None
  513.     poly = quarkx.newobj("poly:p")
  514.     for face in nodeBox(o):
  515.         poly.appenditem(face)
  516.     center=(quarkx.vect(o['mins'])+quarkx.vect(o['maxs']))/2
  517.     parent = o.parent
  518.     current = o
  519. #    while 0:
  520.     while parent is not None and parent.type==":bspnode":
  521.         plane = parent.findallsubitems("",":bspplane")[0]
  522.         face=quarkx.newobj('%s:f'%plane.shortname)
  523. #        debug(' plane: '+plane.name)
  524.         norm = quarkx.vect(plane['norm'])
  525.         dist, = plane['dist']
  526.         orth = orthogonalvect(norm)
  527.         cross = orth^norm
  528.         org = dist*norm
  529.         face.setthreepoints((org, org+orth,org+cross),0)
  530.         if face.normal*norm<0:
  531.             face.swapsides()
  532.         if current.name[:5]=='First':
  533.             face.swapsides()
  534.         poly.appenditem(face)
  535. #        if not face in poly.faces:
  536. #            poly.removeitem(face)
  537.         current = parent
  538.         parent = parent.parent
  539.     return poly
  540.  
  541. class NodeType(quarkpy.mapentities.GroupType):
  542.     "Bsp Nodes"
  543.      
  544.     def drawback(o, editor, view, mode):
  545.         view.drawmap(nodePoly(o),mode)
  546.  
  547.     def menu(o, editor):
  548.          
  549.         #
  550.         # For debugging
  551.         #
  552.         def bboxPoly(m, o=o, editor=editor):
  553.             poly = nodePoly(o)
  554.             undo=quarkx.action()
  555.             undo.put(o,poly)
  556.             editor.ok(undo,"Add bbox Poly")
  557.  
  558.         polyItem=qmenu.item("Add BBox poly", bboxPoly)
  559.  
  560.  
  561. #
  562. #  hmm this one doesn't actually seem to be so straightforward
  563. #
  564. #        def showFaceClick(m,o=o,editor=editor):
  565. #            faces=o.faces
  566. #            debug(`faces`)
  567. #            
  568. #        faceItem=qmenu.item("Show Faces",showFaceClick)
  569.  
  570.         return [polyItem]
  571.  
  572. quarkpy.mapentities.Mapping[":bspnode"] = NodeType()
  573. quarkpy.mapentities.Mapping[":bspplane"] = PlaneType()
  574. quarkpy.mapentities.Mapping[":bsphull"] = HullType()
  575.  
  576. def bspfinishdrawing(editor, view, oldmore=quarkpy.qbaseeditor.BaseEditor.finishdrawing):
  577. #    debug('start draw')
  578.     from plugins.tagging import drawsquare
  579.     oldmore(editor, view)
  580.     sel = editor.layout.explorer.uniquesel
  581.     if sel is None:
  582.       return
  583.     cv = view.canvas()
  584.     cv.pencolor = MapColor("Duplicator")
  585.     if sel.type==":bspplane":
  586. #        debug('plane')
  587.         dist, = sel["dist"]
  588.         norm = quarkx.vect(sel["norm"])
  589.         pos = dist*norm         
  590.         p1 = view.proj(pos)
  591.         p2 = view.proj(pos+96*norm)
  592.         drawsquare(cv,p1,10)
  593.         cv.line(p1, p2)    
  594.  
  595. quarkpy.qbaseeditor.BaseEditor.finishdrawing = bspfinishdrawing
  596.